/*This source code copyrighted by Lazy Foo' Productions (2004-2009) and may not
be redestributed without written permission.*/

//The headers
#include <GL/gl.h>
#include <GL/glu.h>
#include "SDL/SDL.h"
#include "SDL/SDL_opengl.h"

#include "FTGL/ftgl.h"
//#include "FTGL/FTFont.h"

#include "Graph.h"
#include "Cell.h"
#include "Port.h"


#include <stdlib.h>

#include <iostream>

#include <math.h>

#define PI 3.14159265

#define FONTSCALE 2

#define cellR 1
#define portR 0.2

using namespace std;

//Screen attributes
const int SCREEN_WIDTH = 1024;
const int SCREEN_HEIGHT = 768;
const int SCREEN_BPP = 32;

//The frame rate
const int FRAMES_PER_SECOND = 60;

//The attributes of the square
const int SQUARE_WIDTH = 20;
const int SQUARE_HEIGHT = 20;

FTFont *font[2];

bool quit;

Graph graph;


float camera[3];
float cameraGo[3];
float cameraV[3];
float vIntersect[3];

//Event handler
SDL_Event event;



//The timer
class Timer
{
    private:
    //The clock time when the timer started
    int startTicks;

    //The ticks stored when the timer was paused
    int pausedTicks;

    //The timer status
    bool paused;
    bool started;

    public:
    //Initializes variables
    Timer();

    //The various clock actions
    void start();
    void stop();
    void pause();
    void unpause();

    //Gets the timer's time
    int get_ticks();

    //Checks the status of the timer
    bool is_started();
    bool is_paused();
};

bool init_GL()
{

	glEnable(GL_LINE_SMOOTH);


    //Set clear color
    glClearColor( 1,1,1,1 );
    glClearDepth(1.0);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glShadeModel(GL_SMOOTH);

    glEnable(GL_COLOR_MATERIAL);

    //Set projection
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
   // glOrtho( 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1, 1 );

    gluPerspective(45, (float) SCREEN_WIDTH / (float) SCREEN_HEIGHT , 1 , 100);

    //Initialize modelview matrix
    glMatrixMode( GL_MODELVIEW );
    //glLoadIdentity();

    //If there was any errors
    if( glGetError() != GL_NO_ERROR )
    {
        return false;
    }

    //If everything initialized
    return true;
}

bool init()
{
    //Initialize SDL
    if( SDL_Init( SDL_INIT_EVERYTHING ) < 0 )
    {
        return false;
    }

    //Create Window
    if( SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_OPENGL ) == NULL )
    {
        return false;
    }

    //Initialize OpenGL
    if( init_GL() == false )
    {
        return false;
    }

    font[0] = new FTTextureFont("src/Arial.ttf");
    font[1] = new FTPolygonFont("src/Arial.ttf");
   // font[2] = new ("src/Arial.ttf");

    if(font[0]->Error() || font[1]->Error())
    {
    	cout << "FONT DIDNT LOAD" << endl;
    	return -1;
    }

    font[0]->FaceSize(80);
   // font[0]->Depth(10);
   // font[0]->Outset(0,3);
   // font100]->CharMap(ft_encoding_unicode);

    font[1]->FaceSize(1);
    //font[1]->CharMap(ft_encoding_unicode);
    //FTGLTextureFont

    //Set caption
    SDL_WM_SetCaption( "GraphPaper", NULL );


    //Set up camera
    camera[0] = 0;
    camera[1] = 0;
    camera[2] = 0;

    cameraGo[0] = 0;
    cameraGo[1] = 0;
    cameraGo[2] = -10;

    cameraV[0] = 0;
    cameraV[1] = 0;
    cameraV[2] = 0;

    vIntersect[0] = 0;
    vIntersect[1] = 0;
    vIntersect[2] = 0;


    graph.addCell("hello",-2,-3);

   // cout << "cell: " << graph.cells[0].getX() << "," << graph.cells[0].getY() << endl;

   	 graph.cells[0].addPort();
   	 graph.cells[0].addPort();
   	 graph.cells[0].addPort();



   	 graph.addCell("bye",2,3);
   	 graph.cells[1].addPort();
  	 graph.cells[1].addPort();
   	 graph.cells[1].addPort();


   	graph.addCell("maybe",6,6);
   	graph.cells[2].addPort();

   //	cout << "cell: " << graph.cells[0].getX() << "," << graph.cells[0].getY() << endl;


  // 	cout << "cells size " << graph.cells.size() << endl;

   	 //cout << graph.cells[0].ports.size() << endl;

//   	cout << graph.cells[0].getX() << " , " << graph.cells[0].getY() << endl;

 //  	cout << "A1:   " << &graph.cells[0] << endl;
 //	cout << "A1:   " << &graph.cells[0] << endl;
 //	cout << "A2:   " << graph.cells[0].ports[0].getCell() << endl;

 //  	 cout << graph.cells[0].ports[2].getCell()->getX() << " , " << graph.cells[0].ports[2].getCell()->getY() << "  " << graph.cells[0].ports[2].getCell()->getName() << endl;

 //  	 cout << graph.cells[1].ports[1].getCell()->getX() << " , " << graph.cells[1].ports[1].getCell()->getY() << endl;

 //  	cout << graph.cells[2].ports[0].getCell()->getX() << " , " << graph.cells[2].ports[0].getCell()->getY() << endl;

   	 //cout << "cells: " << graph.cells.size() <<endl;

  	 graph.addEdge(&graph.cells[0].ports[0], &graph.cells[1].ports[1]);

   	 cout << "p"<< graph.edges[0].getPort(0)->getName() << "    cellX: " << graph.edges[0].getPort(0)->getCell()->getX() << "       cellY: " << graph.edges[0].getPort(0)->getCell()->getY() << endl;

   	 cout << "p"<< graph.edges[0].getPort(1)->getName() << "    cellX: " << graph.edges[0].getPort(1)->getCell()->getX() << "       cellY: " << graph.edges[0].getPort(1)->getCell()->getY() << endl;

   //	cout << "cell: " << graph.cells[1].getX() << "," << graph.cells[1].getY() << endl;

   //	cout << "e    " << graph.edges[0].getPort(1)->getCell()->getY() << endl;


    return true;
}

void drawCircle(float x, float y, float z, float r, int step,float fill[4],float line[4])
{

	glColor3f(fill[0],fill[1],fill[2]);
	glBegin(GL_POLYGON);

	for(int i = 0 ; i<360 ; i=i+step)
	{
		glVertex3f(x+r*cos(i*PI/180),y+r*sin(i*PI/180),z);
	}

	glEnd();


	glColor3f(line[0],line[1],line[2]);
	glBegin(GL_LINE_STRIP);

	for(int i = 0 ; i<=360 ; i=i+step)
	{
		glVertex3f(x+r*cos(i*PI/180),y+r*sin(i*PI/180),z);
	}



	glEnd();
}

void drawLine(float x1, float y1, float x2, float y2, float z, bool dotted,float col[4])
{

	glColor4f(col[0],col[1],col[2],col[3]);

	if(dotted)
		glBegin(GL_LINES);
	else
		glBegin(GL_LINES);

	glVertex3f(x1, y1, z);
	glVertex3f(x2, y2, z);

	glEnd();
}

bool drawText(const char *c, float x, float y, float z, float s = 1, float r = 0 , float g = 0 , float b = 0)
{

	glPushMatrix();

	glColor3f(r,g,b);

	glTranslatef(x,y,z);

	glScalef(s/FONTSCALE,s/FONTSCALE,s/FONTSCALE);

	font[1]->Render(c);

	glPopMatrix();

}

void drawCellsAndPorts()
{

	float black[] = {0,0,0,1};
	float white[] = {1,1,1,1};

	float lx, ly;


	for(int i = 0 ; i < graph.cells.size() ; i++)
	{

		//cout << "drawing cell: " << graph.cells[i].getName()<< endl;
		//draw the cell itself
		drawCircle(graph.cells[i].getX(), graph.cells[i].getY(),0.01, cellR, 10, white, black);

		drawText(graph.cells[i].getName().c_str(),graph.cells[i].getX(),graph.cells[i].getY(),0.18);

		//go through each port and draw it

		for(int j = 0 ; j < graph.cells[i].ports.size() ; j++)
		{
			//drawCircle(graph.cells[i].getX() + sin((360*j/graph.cells[i].ports.size())*PI/180)*0.5,  graph.cells[i].getY() +cos((360*j/graph.cells[i].ports.size())*PI/180)*0.5, 0.1, 30, black, white);
			drawCircle(graph.cells[i].getX() + graph.cells[i].ports[j].getX(), graph.cells[i].getY() + graph.cells[i].ports[j].getY(),0.15 , portR, 15, white, black);

			//QString s = QString();
			//renderText(graph.cells[i].getX() + graph.cells[i].ports[j].getX(), graph.cells[i].getY() + graph.cells[i].ports[j].getY(), 0.0, QString::fromStdString(graph.cells[i].ports[j].getName()));


			FTBBox box = font[1]->BBox(graph.cells[i].ports[j].getName().c_str());

			//cout << box.Lower().Xf() << "," << box.Lower().Yf() << "," << box.Upper().Xf()/FONTSCALE << "," << box.Upper().Yf()/FONTSCALE << endl;

			lx = (box.Lower().Xf() + box.Upper().Xf())/(2*FONTSCALE);
			ly = (box.Lower().Yf() + box.Upper().Yf())/(2*FONTSCALE);

			//cout << box.Lower().Xf() << "," << box.Lower().Yf() << endl;

			drawText(graph.cells[i].ports[j].getName().c_str(),graph.cells[i].getX() + graph.cells[i].ports[j].getLabelPos()->getX()-lx, graph.cells[i].getY() + graph.cells[i].ports[j].getLabelPos()->getY()-ly,0.16);

			// RENDER TEXT REPLACEMTN?

		}

	}



}

void drawEdges()
{
	float black[] = {0,0,0,1};
		float white[] = {1,1,1,1};

	float red[] = {1,0,0,1};

	for(int i = 0 ; i < graph.edges.size() ; i++)
	{
		Port* p1 = graph.edges[i].getPort(0);
		Port* p2 = graph.edges[i].getPort(1);

		//cout << "edge pos  " << p2->getCell()->getX() << "," << p2->getX() << "     -     " << p2->getCell()->getY() <<","<< p2->getY() << endl;
		//cout << "mo" << endl;

		if(graph.edges[i].getPort(0)->getCell()==NULL)
		{
			cout << "its null" << endl;
		}
		else
		{
			cout << "it's not null. wtf?" << endl;
		}



		cout << graph.edges[i].getPort(0)->getCell() << endl;
		cout << graph.edges[i].getPort(0)->getCell()->getName() << endl;
		cout << graph.edges[i].getPort(0)->getCell()->getX() << " , " << p1->getCell()->getY() << endl;

		drawCircle(p1->getCell()->getX() /*+ p1->getX()*/, p1->getCell()->getX() /*+ p1->getX()*/,0.2, portR , 15, black, black);

		//cout << p1->getCell()->getX() + p1->getX() << "," << p1->getCell()->getY() + p1->getY() << endl;


		//drawLine(p1->getCell()->getX() + p1->getX(), p1->getCell()->getY() + p1->getY(), p2->getCell()->getX() + p2->getX(), p2->getCell()->getY() + p2->getY(),0.16, true, red);



	}

}

void drawGrid()
{
	glColor4f(0.7,0.7,0.7,1);
	glBegin(GL_LINES);
	for(int x = camera[0]-20 ; x < camera[0]+21 ; x++)
	{

		glVertex3f(x,-100,0);
		glVertex3f(x,100,0);

	}

	for(int x = camera[1]-20 ; x < camera[1]+21 ; x++)
	{

		glVertex3f(-100,x,0);
		glVertex3f(100,x,0);

	}
	glEnd();

	glColor4f(0,0,1,1);
	//renderText(0, 0, 0.1, "(0,0)");
}

bool drawScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	glTranslated(-camera[0] , -camera[1], camera[2]);



	//cout << "(" << vIntersect[0] << "," << vIntersect[1]  << ")" << endl;
	//cout << "(" << cameraV[0] << "," << cameraV[1]  << ")" << endl;
	//

}

void clean_up()
{
    //Quit SDL
    SDL_Quit();
}

void getElemAtMouse(int xm, int ym)
{
	int viewport[4];
	double mvmatrix[16];
	double projmatrix[16];
	int realy = 0;// GL y coord pos
	double wcoord[4];
	double wcoord2[4];



	int x = xm;
	int y = ym;

	glGetIntegerv(GL_VIEWPORT, viewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);


	realy = viewport[3] - (int) y - 1;

	gluUnProject((double) x, (double) realy, 0.0, //  replaced 0.0 by z
					 mvmatrix,
					 projmatrix,
					 viewport,
					 &wcoord[0],&wcoord[1],&wcoord[2]);


	wcoord2[0]=wcoord[0];
	wcoord2[1]=wcoord[1];
	wcoord2[2]=wcoord[2];
	wcoord2[3]=wcoord[3];

	gluUnProject((double) x, (double) realy, 1.0, //
					 mvmatrix,
					 projmatrix,
					 viewport,
					 &wcoord[0],&wcoord[1],&wcoord[2]);


	// Returns in (fX, fY) the location on the plane (P1,P2,P3) of the intersection with the ray (R1, R2)
	// First compute the axes
	float r1[3],r2[3],p1[3],p2[3],p3[3],v1[3],v2[3],v3[3];

	r1[0] = wcoord[0];
	r1[1] = wcoord[1];
	r1[2] = wcoord[2];

	r2[0] = wcoord2[0];
	r2[1] = wcoord2[1];
	r2[2] = wcoord2[2];

	p1[0]=0;p1[1]=0;p1[2]=0;
	p2[0]=0;p2[1]=1;p2[2]=0;
	p3[0]=2;p3[1]=1;p3[2]=0;



	v1[0]=p2[0]-p1[0];// = P2 - P1;
	v1[1]=p2[1]-p1[1];
	v1[2]=p2[2]-p1[2];

	v2[0]=p3[0]-p1[0];// = P3 - P1;
	v2[1]=p3[1]-p1[1];
	v2[2]=p3[2]-p1[2];

	//cross product
	//x=(a.y*b.z)-(a.z*b.y);
	//y=(a.x*b.z)-(a.z*b.x);
	//z=(a.x*b.y)-(a.y*b.x);

	 // = CrossProduct ( V1, V2);

	v3[0] = (v1[1]*v2[2]) - (v1[2]*v2[1]);
	v3[1] = (v1[0]*v2[2]) - (v1[2]*v2[0]);
	v3[2] = (v1[0]*v2[1]) - (v1[1]*v2[0]);


	// Project ray points R1 and R2 onto the axes of the plane. (This is equivalent to a rotation.)


	float r1p1[3];
	float r2p1[3];

	r1p1[0] = r1[0] - p1[0];
	r1p1[1] = r1[1] - p1[1];
	r1p1[2] = r1[2] - p1[2];

	r2p1[0] = r2[0] - p1[0];
	r2p1[1] = r2[1] - p1[1];
	r2p1[2] = r2[2] - p1[2];

	float vRotRay1[3],vRotRay2[3];

	//dot prod  (x*a.x)+(y*a.y)+(z*a.z);
	vRotRay1[0] = v1[0]*r1p1[0] + v1[1]*r1p1[1] + v1[2]*r1p1[2];
	vRotRay1[1] = v2[0]*r1p1[0] + v2[1]*r1p1[1] + v2[2]*r1p1[2];
	vRotRay1[2] = v3[0]*r1p1[0] + v3[1]*r1p1[1] + v3[2]*r1p1[2];

	vRotRay2[0] = v1[0]*r2p1[0] + v1[1]*r2p1[1] + v1[2]*r2p1[2];
	vRotRay2[1] = v2[0]*r2p1[0] + v2[1]*r2p1[1] + v2[2]*r2p1[2];
	vRotRay2[2] = v3[0]*r2p1[0] + v3[1]*r2p1[1] + v3[2]*r2p1[2];

		// Return now if ray will never intersect plane (they're parallel)
	if(vRotRay1[2] == vRotRay2[2])
		cout << "NEVER INTERSECT" << endl;




	// Find 2D plane coordinates (fX, fY) that the ray interesects with
	float fPercent = vRotRay1[2] / (vRotRay2[2]-vRotRay1[2]);


	// Note that to find the 3D point on the world-space ray use this
	//= R1 + (R1-R2) * fPercent;
	vIntersect[0] = r1[0]+(r1[0]-r2[0])*fPercent;
	vIntersect[1] = r1[1]+(r1[1]-r2[1])*fPercent;
	vIntersect[2] = r1[2]+(r1[2]-r2[2])*fPercent;


	//cout << "Intersect at: " << vIntersect[0] << "," << vIntersect[1] << "," << vIntersect[2] << endl;


	//vIntersect.x = vIntersect.x;
	//vIntersect.y = vIntersect.y;

	// UNCOMMENT THESE TO GET 3D WORLD COORD OF MOUSE on -10z PLANE
	//System.out.println("HITTING at:");
	//System.out.println(""+vIntersect.x+":"+vIntersect.y+":"+vIntersect.z);
	//System.out.println(camera.z);
	//System.out.println(""+fX+":"+fY);


}

void handle_input()
{
    //If a key was pressed
    if( event.type == SDL_KEYDOWN )
    {
        //Adjust the velocity
        switch( event.key.keysym.sym )
        {
            case SDLK_q: cameraV[2]=1; break;
            case SDLK_e: cameraV[2]=-1; break;
            case SDLK_w: cameraV[1]=0.5; break;
            case SDLK_s: cameraV[1]=-0.5; break;
            case SDLK_d: cameraV[0]=0.5; break;
            case SDLK_a: cameraV[0]=-0.5; break;

            case SDLK_ESCAPE: quit = true; break;
          //  case SDLK_DOWN: yVel += SQUARE_HEIGHT / 2; break;
          //  case SDLK_LEFT: xVel -= SQUARE_WIDTH / 2; break;
          //  case SDLK_RIGHT: xVel += SQUARE_WIDTH / 2; break;
        }
    }
    //If a key was released
    else if( event.type == SDL_KEYUP )
    {
        //Adjust the velocity
        switch( event.key.keysym.sym )
        {
			case SDLK_q: cameraV[2]=0; break;
			case SDLK_e: cameraV[2]=0; break;
			case SDLK_w: cameraV[1]=0; break;
			case SDLK_s: cameraV[1]=0; break;
			case SDLK_d: cameraV[0]=0; break;
			case SDLK_a: cameraV[0]=0; break;
          //  case SDLK_UP: yVel += SQUARE_HEIGHT / 2; break;
          //  case SDLK_DOWN: yVel -= SQUARE_HEIGHT / 2; break;
          //  case SDLK_LEFT: xVel += SQUARE_WIDTH / 2; break;
          //  case SDLK_RIGHT: xVel -= SQUARE_WIDTH / 2; break;
        }
    }



    if( event.type == SDL_MOUSEMOTION )
    {
    	//cout << "MOUSE" << endl;
    	getElemAtMouse(event.motion.x,event.motion.y);
    }
}

float curveValue( float newV, float oldV , float inc);

bool updateCamera()
{
	cameraGo[0] += cameraV[0];
	cameraGo[1] += cameraV[1];
	cameraGo[2] += cameraV[2];

	camera[0]=curveValue(cameraGo[0], camera[0], 10);
	camera[1]=curveValue(cameraGo[1], camera[1], 10);
	camera[2]=curveValue(cameraGo[2], camera[2], 10);

	return true;
}


int main( int argc, char *argv[] )
{
    //Quit flag
    quit = false;

    //Initialize
    if( init() == false )
    {
        return 1;
    }


    //The frame rate regulator
    Timer fps;

	//Wait for user exit
	while( quit == false )
	{
        //Start the frame timer
        fps.start();

        //While there are events to handle
		while( SDL_PollEvent( &event ) )
		{
            //Handle key presses
            handle_input();

			if( event.type == SDL_QUIT )
			{
                quit = true;
            }
		}

	    //Update Camera
		updateCamera();


	    //Clear the screen
	    drawScene();

	    //Draw Grid

	    drawGrid();  // WHAT IS RENDER TXT????????

	    //draw cells and ports



	   drawCellsAndPorts();
	    drawEdges();


	    drawText("(0,0)",0,0,0.1,0.4);


	   // drawText("Helloooo",1,1,0.1,1);

	    //drawText("Hello?",2,2,0.1,1,0,0,1);


	    //Update screen
	    SDL_GL_SwapBuffers();

        //Cap the frame rate
        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }
	}

	//Clean up
	clean_up();

	return 0;
}



float curveValue( float newV, float oldV , float inc)
{
	if(inc>1)
	{
		//if((oldV+360)-newV<newV-oldV)
		//	oldV=360+oldV;

		//if((newV+360)-oldV<oldV-newV)
		//	newV=360+newV;

		oldV=oldV-(oldV-newV)/inc;
	}
	if(inc<=1)
		return newV;

	return oldV;
}


Timer::Timer()
{
    //Initialize the variables
    startTicks = 0;
    pausedTicks = 0;
    paused = false;
    started = false;
}

void Timer::start()
{
    //Start the timer
    started = true;

    //Unpause the timer
    paused = false;

    //Get the current clock time
    startTicks = SDL_GetTicks();
}

void Timer::stop()
{
    //Stop the timer
    started = false;

    //Unpause the timer
    paused = false;
}

void Timer::pause()
{
    //If the timer is running and isn't already paused
    if( ( started == true ) && ( paused == false ) )
    {
        //Pause the timer
        paused = true;

        //Calculate the paused ticks
        pausedTicks = SDL_GetTicks() - startTicks;
    }
}

void Timer::unpause()
{
    //If the timer is paused
    if( paused == true )
    {
        //Unpause the timer
        paused = false;

        //Reset the starting ticks
        startTicks = SDL_GetTicks() - pausedTicks;

        //Reset the paused ticks
        pausedTicks = 0;
    }
}

int Timer::get_ticks()
{
    //If the timer is running
    if( started == true )
    {
        //If the timer is paused
        if( paused == true )
        {
            //Return the number of ticks when the timer was paused
            return pausedTicks;
        }
        else
        {
            //Return the current time minus the start time
            return SDL_GetTicks() - startTicks;
        }
    }

    //If the timer isn't running
    return 0;
}

bool Timer::is_started()
{
    return started;
}

bool Timer::is_paused()
{
    return paused;
}
